package com.dynamic.mybatis.core.parser.field;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.dynamic.mybatis.core.enums.Type;
import com.dynamic.mybatis.core.enums.TypeAlias;
import com.dynamic.mybatis.core.metadata.*;
import com.dynamic.mybatis.core.mock.Mock;
import com.dynamic.mybatis.core.parser.DatabaseMetaDataHelper;
import com.dynamic.mybatis.core.session.DynamicSqlSession;
import com.dynamic.mybatis.core.utils.BeanUtils;
import com.dynamic.mybatis.core.utils.CollectionUtils;
import com.dynamic.mybatis.core.utils.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xs
 * @Date 2022/11/17 11:05
 */
public abstract class DynamicMappedStatementDataBaseFieldParser extends DatabaseMetaDataHelper {

    public Configuration configuration;
    protected DynamicSqlSession dynamicSqlSession;

    public DynamicMappedStatementDataBaseFieldParser(DynamicSqlSession dynamicSqlSession) {
        this.dynamicSqlSession = dynamicSqlSession;
        this.configuration = dynamicSqlSession.getSqlSession().getConfiguration();
    }

    public static void printFieldPropertes(List<Map<String, Object>> fieldPropertes) {
        System.out.println();
        System.out.println("-------------------------------------------------------------------------------------------------------");
        List<Integer> maxLength = new ArrayList<>();
        int fixed = 4;
        for (Map<String, Object> propertes : fieldPropertes) {
            int index = 0;
            for (Map.Entry<String, Object> entry : propertes.entrySet()) {
                int keyLength = String.valueOf(entry.getKey()).length();
                int valuelength = String.valueOf(entry.getValue()).length();
                if (maxLength.size() <= index) {
                    int max = keyLength > valuelength ? keyLength : valuelength;
                    maxLength.add(max);
                } else {
                    int a = maxLength.get(index) > keyLength ? maxLength.get(index) : keyLength;
                    int max = a > valuelength ? a : valuelength;
                    maxLength.set(index, max);
                }
                index++;
            }
        }
        maxLength = maxLength.stream().map(item -> item + fixed).collect(Collectors.toList());
        int index = 0;
        for (String key : fieldPropertes.get(0).keySet()) {
            System.out.print(String.format("%-" + maxLength.get(index) + "s", key));
            index++;
        }
        System.out.println();
        for (Map<String, Object> propertes : fieldPropertes) {
            index = 0;
            for (Map.Entry<String, Object> entry : propertes.entrySet()) {
                System.out.print(String.format("%-" + maxLength.get(index) + "s", entry.getValue()));
                index++;
            }
            System.out.println();
        }
        System.out.println("-------------------------------------------------------------------------------------------------------");
        System.out.println();
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public Map<String, Map<String, List<String>>> queryTableColumn(Map<String, Connection> connections) {
        Map<String, Map<String, List<String>>> dataSouces = new HashMap<>();
        for (Map.Entry<String, Connection> entry : connections.entrySet()) {
            dataSouces.put(entry.getKey(), queryTableColumn(entry.getKey(), entry.getValue()));
        }
        return dataSouces;
    }

    public List<String> queryTable(String dbName, Connection connection) {
        List<String> tables = new ArrayList<>();
        try {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSet tableResultSet = dbMetaData.getTables(dbName, null, null, new String[]{"TABLE"});
            while (tableResultSet.next()) {
                String tableName = tableResultSet.getString("TABLE_NAME");
                tables.add(tableName);
            }
            tableResultSet.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return tables;
    }

    /**
     * @param dbName     数据库名
     * @param connection 链接
     * @return key tableName ,value column集合
     */
    public Map<String, List<String>> queryTableColumn(String dbName, Connection connection) {
        Map<String, List<String>> tables = new HashMap<>();
        try {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSet tableResultSet = dbMetaData.getTables(dbName, null, null, new String[]{"TABLE"});
            while (tableResultSet.next()) {
                String tableName = tableResultSet.getString("TABLE_NAME");
                ResultSet columnResultSet = dbMetaData.getColumns(dbName, null, tableName, null);
                while (columnResultSet.next()) {
                    String columnName = columnResultSet.getString("COLUMN_NAME");
                    List<String> columnNames = tables.get(tableName);
                    if (columnNames == null) {
                        columnNames = new ArrayList<>();
                        tables.put(tableName, columnNames);
                    }
                    columnNames.add(columnName);
                }
                columnResultSet.close();
            }
            tableResultSet.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return tables;
    }

    protected List<MappedStatementMetaField> getDataBaseFieldUpdateMappedStatementField(DynamicSqlSession dynamicSqlSession, DynamicMappedStatement dms, MappedStatement mappedStatement, Object parameter) {
        List<MappedStatementTable> tables = getDataBaseFieldUpdateMappedStatement(dynamicSqlSession, dms, mappedStatement, parameter);
        if (CollectionUtils.isEmpty(tables)) {
            return Collections.EMPTY_LIST;
        }
        return tables.stream().flatMap(t -> t.getFields().stream()).collect(Collectors.toList());
    }

    protected List<MappedStatementTable> getDataBaseFieldUpdateMappedStatement(DynamicSqlSession dynamicSqlSession, DynamicMappedStatement dms, MappedStatement mappedStatement, Object parameter) {
        parameter = parameter == null && CollectionUtils.isNotEmpty(dms.getInputParameter()) ? Mock.mock(dms.getInputParameter().get(0)) : parameter;
        if (dms.isBatch() && parameter != null && (parameter instanceof Collection)) {
            parameter = ((Collection<?>) parameter).stream().findFirst().orElse(null);
        }
        String sql = mappedStatement.getSqlSource().getBoundSql(parameter).getSql();
        String urlSplit[];
        try (Connection connection = dynamicSqlSession.getConnection(dms.getId());) {
            urlSplit = connection.getMetaData().getURL().split(":");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        List<SQLStatement> sqlStatements = SQLUtils.parseStatements(sql, urlSplit[1]);
        Collection<TableStat.Column> tableStatColumns = null;
        for (SQLStatement sqlStatement : sqlStatements) {
            SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(urlSplit[1]);
            sqlStatement.accept(visitor);
            tableStatColumns = visitor.getColumns();
        }
        Set<String> tables = tableStatColumns.stream().map(TableStat.Column::getTable).collect(Collectors.toSet());
        Set<String> columns = tableStatColumns.stream().map(TableStat.Column::getName).collect(Collectors.toSet());
        if (tables != null && tables.size() > 0 && columns != null && columns.size() > 0) {
            return new DatabaseMetaDataHelper().getDatabaseMeta(dynamicSqlSession, (String) dms.getTargetDataSourceKey(), tables.toArray(new String[tables.size()]));
        }
        return null;
    }

    protected List<MappedStatementMetaField> getDataBaseFieldQueryMappedStatement(DynamicSqlSession dynamicSqlSession, DynamicMappedStatement dms, MappedStatement mappedStatement, Object parameter) {

        DynamicConfiguration configuration = (DynamicConfiguration) getConfiguration();
        if (mappedStatement == null) {
            return Collections.EMPTY_LIST;
        }
        List<MappedStatementMetaField> dbFields;
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        List<ParameterMapping> parameterMapping = boundSql.getParameterMappings();
        PreparedStatement ps;
        Statement stmt;
        ResultSet rs;
        try (Connection connection = dynamicSqlSession.getConnection(dms.getId())) {
            if (CollectionUtils.isNotEmpty(parameterMapping)) {
                ps = connection.prepareStatement(sql);
                configuration.newParameterHandler(mappedStatement, parameter, boundSql).setParameters(ps);
                rs = ps.executeQuery();
            } else {
                stmt = connection.createStatement();
                rs = stmt.executeQuery(sql);
            }
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSetMetaData rsMetaData = rs.getMetaData();
            //获取字段信息
            dbFields = getDataBaseField(dbMetaData, rsMetaData);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return dbFields;
    }

    private List<MappedStatementMetaField> getDataBaseField(DatabaseMetaData dbMetaData, ResultSetMetaData rsMetaData) throws SQLException {

        List<MappedStatementMetaField> fields = new ArrayList<>();
        List<Map<String, Object>> printFields = new ArrayList<>();
        for (int i = 1, columnCount = rsMetaData.getColumnCount(); i <= columnCount; i++) {
            String remark = null;
            ResultSet results;
            if (StringUtils.isNotBlank(rsMetaData.getCatalogName(i)) && StringUtils.isNotBlank(rsMetaData.getTableName(i))
                    && (results = dbMetaData.getColumns(rsMetaData.getCatalogName(i), rsMetaData.getSchemaName(i), rsMetaData.getTableName(i), rsMetaData.getColumnName(i))) != null
                    && !results.isLast()
            ) {
                while (results.next()) {
                    remark = results.getString("REMARKS");
                }
            }

            MappedStatementMetaField field = new MappedStatementMetaField();
            field.setDataBase(rsMetaData.getCatalogName(i));
            field.setSchema(rsMetaData.getSchemaName(i));
            field.setTableName(rsMetaData.getTableName(i));
            field.setColumn(rsMetaData.getColumnLabel(i));
            field.setField(StringUtils.underlineToCamel(rsMetaData.getColumnLabel(i)));
            field.setVisible(Boolean.TRUE);
            field.setName((StringUtils.isNotBlank(remark) ? remark : field.getField()));
            field.setJavaTypeClass(TypeAlias.getType(rsMetaData.getColumnClassName(i)));
            field.setJavaType(rsMetaData.getColumnClassName(i));
            JDBCType jdbcType = JDBCType.valueOf(rsMetaData.getColumnType(i));
            Type type = Type.getType(jdbcType);

            field.setType(type.getJavaTypeAlias());
            field.setJdbcType(JDBCType.valueOf(rsMetaData.getColumnType(i)));
            MappedStatementFieldValidator validator = new MappedStatementFieldValidator();
            validator.setRequired(rsMetaData.isNullable(i) == 1);
            validator.setMin(type.getMin());
            validator.setMax(type.getMax());
            if (Type.StandardType.VARCHAR.javaTypeAlias.equals(field.getType())) {
                validator.setMaxLength(rsMetaData.getPrecision(i));
            } else {
                validator.setMaxLength(type.getLength());
            }
            if (field.getJdbcType() == JDBCType.TIMESTAMP) {
                validator.setDatePattern("yyyy-MM-dd HH:mm:ss");
            } else if (field.getJdbcType() == JDBCType.DATE) {
                validator.setDatePattern("yyyy-MM-dd");
            } else if (field.getJdbcType() == JDBCType.TIME) {
                validator.setDatePattern("HH:mm:ss");
            }
            field.setValidator(validator);
            field.setSort((byte) i);

            Map<String, Object> value = new LinkedHashMap<>();
            value.put("catalogName", rsMetaData.getCatalogName(i));
            value.put("tableName", rsMetaData.getTableName(i));
            value.put("columnName", rsMetaData.getColumnName(i));
            value.put("columnLabel", rsMetaData.getColumnLabel(i));
            value.put("columnType", rsMetaData.getColumnType(i));
            value.put("columnTypeName", rsMetaData.getColumnTypeName(i));
            value.put("columnClassName", rsMetaData.getColumnClassName(i));
            value.put("precision", rsMetaData.getPrecision(i));
            value.put("columnDisplaySize", rsMetaData.getColumnDisplaySize(i));
            value.put("isNullable", rsMetaData.isNullable(i));
            value.put("schemaName", rsMetaData.getSchemaName(i));
            value.put("REMARKS", remark);
            printFields.add(value);
            fields.add(field);
        }
        //获取主键
        setPrimaryKey(dbMetaData, fields);
        //格式化输出字段信息
        printFieldPropertes(printFields);
        return fields;
    }

    //设置表主键
    private void setPrimaryKey(DatabaseMetaData dbMetaData, List<MappedStatementMetaField> fields) throws SQLException {
        if (CollectionUtils.isEmpty(fields)) {
            return;
        }

        Map<String, MappedStatementField> map = fields.stream().collect(Collectors.toMap(f -> f.getDataBase() + f.getSchema() + f.getTableName(), f -> f, (f1, f2) -> f2));
        for (MappedStatementField field : map.values()) {
            ResultSet resultSet = dbMetaData.getPrimaryKeys(field.getDataBase(), field.getSchema(), field.getTableName());
            while (resultSet.next()) {
                String primaryKeyColumn = resultSet.getString("COLUMN_NAME");
                MappedStatementField pkFieldVo = fields.stream().filter(f ->
                        Objects.equals(f.getDataBase(), field.getDataBase())
                                && Objects.equals(f.getSchema(), field.getSchema())
                                && Objects.equals(f.getTableName(), field.getTableName())
                                && Objects.equals(f.getField(), primaryKeyColumn)
                ).findFirst().orElse(null);
                if (pkFieldVo != null) {
                    pkFieldVo.setPrimaryKey(Boolean.TRUE);
                }
            }
        }
    }

    protected MappedStatementMetaField mergeMappedStatementMetaField(MappedStatementMetaField master, MappedStatementMetaField slave) {
        if (master == null) {
            return slave;
        }
        if (slave == null || CollectionUtils.isEmpty(slave.getChildren())) {
            return master;
        }
        for (MappedStatementMetaField field2 : slave.getChildren()) {
            MappedStatementMetaField masterField = null;
            for (MappedStatementMetaField field1 : master.getChildren()) {
                if (Objects.equals(field1.getField(), field2.getField())) {
                    masterField = field1;
                    break;
                }
            }
            if (masterField == null) {
                master.getChildren().add(field2);
            } else if (CollectionUtils.isNotEmpty(field2.getChildren())) {
                mergeMappedStatementMetaField(masterField, field2);
            }
        }
        return master;
    }

    protected List<MappedStatementMetaField> mergeDataBseToJavaBatch(List<MappedStatementMetaField> dbFields, List<MappedStatementMetaField> javaFields, boolean ignoreMergeRequired) {
        if (CollectionUtils.isEmpty(javaFields)) {
            return dbFields;
        }
        if (CollectionUtils.isEmpty(dbFields)) {
            return javaFields;
        }
        for (MappedStatementMetaField dbField : dbFields) {
            mergeToJavaBatchRecursion(dbField, javaFields, ignoreMergeRequired);
        }
        return javaFields;
    }

    /**
     * @param dbField             数据库字段信息
     * @param javaFields          mybatis result解析的字段 或者 DynamicMappedStatement 字段
     * @param ignoreMergeRequired 忽略必填项合并 以
     */
    private void mergeToJavaBatchRecursion(MappedStatementMetaField dbField, List<MappedStatementMetaField> javaFields, boolean ignoreMergeRequired) {
        for (MappedStatementMetaField javaField : javaFields) {
            if ((StringUtils.isBlank(javaField.getTableName()) || Objects.equals(dbField.getTableName(), javaField.getTableName())) && dbField.getField().equals(javaField.getField())) {
                boolean required = javaField.getValidator() != null ? javaField.getValidator().isRequired() : false;
                BeanUtils.copy(dbField, javaField);
                if (ignoreMergeRequired) {
                    javaField.getValidator().setRequired(required);
                }
            }
            if (CollectionUtils.isNotEmpty(javaField.getChildren())) {
                mergeToJavaBatchRecursion(dbField, javaField.getChildren(), ignoreMergeRequired);
            }
        }
    }

}